/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tchepannou.rails.engine.container;

import com.tchepannou.rails.engine.impl.JMSData;
import com.tchepannou.rails.core.annotation.MessageSource;
import com.tchepannou.rails.core.api.ContainerContext;
import com.tchepannou.rails.core.api.MessageController;
import com.tchepannou.rails.core.exception.InitializationException;
import com.tchepannou.rails.core.service.JMSService;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jms.JMSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Default implementation of {@link ActionControllerProvider}.
 *
 * @author herve
 */
public class MessageControllerContainer
{
    //-- Static Attribute
    private static final Logger LOG = LoggerFactory.getLogger (MessageControllerContainer.class);
    
    
    //-- Attribute
    private ContainerContext _containerContext;
    private Map<String, MessageControllerWrapper> _wrappers = new HashMap<String, MessageControllerWrapper> ();
    private boolean _initialized;
    
    //-- Constructeur
    public MessageControllerContainer ()
    {
    }


    //-- Public method
    public void init (ContainerContext containerContext)
    {
        LOG.info ("Initializing");

        _containerContext = containerContext;
        try
        {
            loadMessageControllerWrappers (_containerContext.getBasePackage () + ".message");
            for (MessageControllerWrapper wrapper : _wrappers.values ())
            {
                if (LOG.isDebugEnabled ())
                {
                    LOG.debug ("Initializing " + wrapper);
                }
                wrapper.init (containerContext);
            }

            _initialized = true;
            LOG.info ("Initialized");
        }
        catch (ClassNotFoundException e)
        {
            throw new InitializationException ("Unable to initialized the MessagingControllerContainer", e);
        }
        catch (IOException e)
        {
            throw new InitializationException ("Unable to initialized the MessagingControllerContainer", e);
        }
    }

    public void destroy ()
    {
        LOG.info ("Destroying");

        for (MessageControllerWrapper wrapper : _wrappers.values ())
        {
            if (LOG.isDebugEnabled ())
            {
                LOG.debug ("Destroying " + wrapper);
            }
            wrapper.destroy ();
        }

        _containerContext = null;
        _wrappers.clear ();
        _initialized = false;

        LOG.info ("Destroyed");
    }

    public boolean isInitialized ()
    {
        return _initialized;
    }

    /**
     * This is the method that send the message to JMS destination
     * 
     * @see ContainerContext#sendMessage(java.lang.String, java.io.Serializable)
     */
    public void sendMessage (String destination, Serializable msg)
        throws JMSException
    {
        if (LOG.isTraceEnabled ())
        {
            LOG.trace("publish(" + destination + "," + msg + ")");
        }

        JMSService js = (JMSService)_containerContext.findService (JMSService.class);
        if (js == null)
        {
            LOG.warn ("Service " + JMSService.class.getName () + " Not available. Can't handle the message");
            return;
        }

        MessageControllerWrapper wrapper = getMessageControllerWrapper (destination);
        if (wrapper == null)
        {
            LOG.warn ("MessageControllerWrapper{" + destination + "} not found");
            return;
        }

        MessageSource src = wrapper.getControllerClass ().getAnnotation (MessageSource.class);
        JMSData data = new JMSData (destination, msg);
        String topic = src.topic ();
        js.sendMessage (topic, data);
    }

    public MessageControllerWrapper getMessageControllerWrapper (String path)
    {
        String name;
        String xpath = path != null ? path.toLowerCase () : "";
        int i = xpath.lastIndexOf ("/");
        if ( i == 0 )
        {
            name = xpath.substring (1);
        }
        else
        {
            String xuri = xpath.substring (0, i);
            int j = xuri.lastIndexOf ("/");
            name = j == 0
                ? xuri.substring (1)
                : xuri.substring (j + 1);
        }
        return _wrappers.get (name.toLowerCase ());
    }



    //-- Private
    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private List<Class> loadMessageControllerWrappers (String packageName)
        throws ClassNotFoundException,
               IOException
    {
        /* Package directory */
        ClassLoader classLoader = Thread.currentThread ().getContextClassLoader ();
        assert classLoader != null;
        String path = packageName.replace ('.', '/');
        Enumeration<URL> resources = classLoader.getResources (path);
        List<File> dirs = new ArrayList<File> ();
        while ( resources.hasMoreElements () )
        {
            URL resource = resources.nextElement ();
            dirs.add (new File (resource.getFile ()));
        }

        /* Load the classes */
        ArrayList<Class> classes = new ArrayList<Class> ();
        for ( File directory: dirs )
        {
            classes.addAll (loadClasses (directory, packageName));
        }
        return classes;
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package topic for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private List<Class> loadClasses (File directory, String packageName)
        throws ClassNotFoundException
    {
        List<Class> classes = new ArrayList<Class> ();
        if ( !directory.exists () )
        {
            return classes;
        }
        File[] files = directory.listFiles ();
        for ( File file: files )
        {
            String name = file.getName ();
            if ( name.endsWith (MessageController.CLASSNAME_SUFFIX + ".class"))
            {
                Class clazz = Class.forName (packageName + '.' + file.getName ().substring (0, file.getName ().length () - 6));
                int modifier = clazz.getModifiers ();
                if (Modifier.isPublic (modifier)
                    && !Modifier.isAbstract (modifier)
                    && !Modifier.isInterface (modifier)
                    && MessageController.class.isAssignableFrom (clazz))
                {
                    registerMessageController (clazz);
                }
            }
        }
        return classes;
    }

    private void registerMessageController (Class<? extends MessageController> clazz)
    {
        MessageSource msg = (MessageSource)clazz.getAnnotation (MessageSource.class);
        if (msg != null)
        {
            MessageControllerWrapper wrapper = new MessageControllerWrapper ();
            wrapper.setControllerClass (clazz);
            wrapper.setDestinationName (msg.topic ());
            
            if (LOG.isDebugEnabled ())
            {
                LOG.debug ("Registering MessagingController: /" + wrapper.getName () + "=" + clazz);
            }
            String name = MessageController.getName (clazz).toLowerCase ();
            _wrappers.put (name, wrapper);
        }
    }
}
